package yahoodata;

import java.math.*;
import java.util.*;

/** Values returned by division/multiplication/square-rooting on raw doubles/floats is a "known unknown".
 *  To precisely control arithmatic and maximize acuracy, the BigDecimal class is liberally used, at the
 *  expense of readable code. As this data could be used for real risk modeling, acurate results is more
 *  important than readable code.
 */
public class Profile {
    private static final MathContext DividePrecision  = MathContext.DECIMAL128;
    private static final MathContext DefaultPrecision = MathContext.UNLIMITED;
//    private static final int         scale     = 50;
//    private static final RoundingMode roundingMode = RoundingMode.HALF_DOWN;
    
    public final String symb, dateStart, dateEnd;
    
    public final float  adjClose,     adjCloseMin,  adjCloseMax,  adjCloseMedian,  adjCloseAvg;
    public final double volumeAvg,    volumeMin,    volumeMax,    volumeMedian,    volumeStdDev;
    public final double dolVolumeAvg, dolVolumeMin, dolVolumeMax, dolVolumeMedian, dolVolumeStdDev;
 
    public final double adjCloseDiffAvg,       adjCloseDiffStdDev, adjCloseStdDev;
    public final double closeOpenPriceDiffAvg, closeOpenPriceDiffStdDev;
    public final double highLowPriceDiffAvg,   highLowPriceDiffStdDev;
    public final double overnightPriceDiffAvg, overnightPriceDiffStdDev;
    public final double volumeDiffAvg,         volumeDiffStdDev;
    public final double dolVolumeDiffAvg,      dolVolumeDiffStdDev;

    
    /** Creates a new instance of SummDesc */
    public Profile(List<TradeEvent> timeSeries) {
        if(timeSeries.size() > 0) {  
            TradeEvent lastEvent = (TradeEvent) timeSeries.get(0);
            
            this.symb      = (String)      lastEvent.symb;
            this.dateStart = ((TradeEvent) timeSeries.get(timeSeries.size() - 1)).date;
            this.dateEnd   = (String)      lastEvent.date;
            this.adjClose  = (float)       lastEvent.adjClose;
            
            BigDecimal sumAdjCloseObj  = new BigDecimal(0, DefaultPrecision);
            BigDecimal sumVolumeObj    = new BigDecimal(0, DefaultPrecision);
            BigDecimal sumDolVolumeObj = new BigDecimal(0, DefaultPrecision);
            
            BigDecimal sumAdjCloseDiffObj       = new BigDecimal(0, DefaultPrecision);
            BigDecimal sumCloseOpenPriceDiffObj = new BigDecimal(0, DefaultPrecision);
            BigDecimal sumHighLowPriceDiffObj   = new BigDecimal(0, DefaultPrecision);
            BigDecimal sumOvernightPriceDiffObj = new BigDecimal(0, DefaultPrecision);
            BigDecimal sumVolumeDiffObj         = new BigDecimal(0, DefaultPrecision);
            BigDecimal sumDolVolumeDiffObj      = new BigDecimal(0, DefaultPrecision);
            
            List<Float>  adjCloseList  = new ArrayList<Float>(timeSeries.size());
            List<Double> volumeList    = new ArrayList<Double>(timeSeries.size());
            List<Double> dolVolumeList = new ArrayList<Double>(timeSeries.size());
            
            int beforeAfterDiffDayCount = 0;
            
            for(int i = 0; i < timeSeries.size(); i++) {
                TradeEvent te = timeSeries.get(i);
                
                sumAdjCloseObj  = sumAdjCloseObj.add(new BigDecimal(te.adjClose, DefaultPrecision ));
                sumVolumeObj    = sumVolumeObj.add(new BigDecimal(te.volume, DefaultPrecision));
                sumDolVolumeObj = sumDolVolumeObj.add(new BigDecimal(te.dolVolume, DefaultPrecision));
                
                sumCloseOpenPriceDiffObj = sumCloseOpenPriceDiffObj.add(new BigDecimal(Math.abs(te.closeOpenPriceDiff), DefaultPrecision));
                sumHighLowPriceDiffObj   = sumHighLowPriceDiffObj.add(new BigDecimal(Math.abs(te.highLowPriceDiff), DefaultPrecision));
                
                adjCloseList.add(Float.valueOf(te.adjClose));
                volumeList.add(Double.valueOf(te.volume));
                dolVolumeList.add(Double.valueOf(te.dolVolume));
                
                if(te.dayBefore) {
                    sumAdjCloseDiffObj       = sumAdjCloseDiffObj.add(new BigDecimal(Math.abs(te.adjCloseDiff), DefaultPrecision), DefaultPrecision);
                    sumOvernightPriceDiffObj = sumOvernightPriceDiffObj.add(new BigDecimal(Math.abs(te.overnightPriceDiff), DefaultPrecision), DefaultPrecision);
                    sumVolumeDiffObj         = sumVolumeDiffObj.add(new BigDecimal(Math.abs(te.volumeDiff), DefaultPrecision), DefaultPrecision);
                    sumDolVolumeDiffObj      = sumDolVolumeDiffObj.add(new BigDecimal(Math.abs(te.dolVolumeDiff), DefaultPrecision), DefaultPrecision);
                    beforeAfterDiffDayCount++;
                }
            }
            
            Collections.sort(adjCloseList);
            Collections.sort(volumeList);
            Collections.sort(dolVolumeList);
            
            this.adjCloseAvg = sumAdjCloseObj.divide(new BigDecimal(timeSeries.size(), DefaultPrecision), DividePrecision).floatValue();
            this.adjCloseMin = (float) adjCloseList.get(0);
            this.adjCloseMax = (float) adjCloseList.get(adjCloseList.size() - 1);
            
            this.volumeAvg = sumVolumeObj.divideToIntegralValue(new BigDecimal(volumeList.size(), DefaultPrecision), DividePrecision).doubleValue();
            this.volumeMin = (double) volumeList.get(0);
            this.volumeMax = (double) volumeList.get(volumeList.size() - 1);
            
            this.dolVolumeAvg = sumDolVolumeObj.divideToIntegralValue(new BigDecimal(dolVolumeList.size(), DefaultPrecision), DividePrecision).doubleValue();
            this.dolVolumeMin = (double) dolVolumeList.get(0);
            this.dolVolumeMax = (double) dolVolumeList.get(dolVolumeList.size() - 1);
            
            if((volumeList.size() % 2) == 1) {
                this.adjCloseMedian  = (float)  adjCloseList.get((int)  (adjCloseList.size()  / 2));
                this.volumeMedian    = (double) volumeList.get((int)    (volumeList.size()    / 2));
                this.dolVolumeMedian = (double) dolVolumeList.get((int) (dolVolumeList.size() / 2));
            } else {
                int a1 = (int) (timeSeries.size()  /  2);
                int b1 = (int) ((timeSeries.size() /  2) - 1);
                
                BigDecimal c1       = new BigDecimal(adjCloseList.get(a1));
                BigDecimal c2       = new BigDecimal(adjCloseList.get(b1));
                this.adjCloseMedian = ((BigDecimal) (c1.add(c2).divide(new BigDecimal(2, DefaultPrecision), DividePrecision))).floatValue();
                
                BigDecimal d1     = new BigDecimal(volumeList.get(a1));
                BigDecimal d2     = new BigDecimal(volumeList.get(b1));
                this.volumeMedian = ((BigDecimal) (d1.add(d2).divideToIntegralValue(new BigDecimal(2, DefaultPrecision), DividePrecision))).doubleValue();
 
                BigDecimal e1        = new BigDecimal(dolVolumeList.get(a1));
                BigDecimal e2        = new BigDecimal(dolVolumeList.get(b1));
                this.dolVolumeMedian = ((BigDecimal) (e1.add(e2).divideToIntegralValue(new BigDecimal(2, DefaultPrecision), DividePrecision))).doubleValue();
            }
            
            this.closeOpenPriceDiffAvg = ((BigDecimal) sumCloseOpenPriceDiffObj.divide(new BigDecimal(timeSeries.size(), DefaultPrecision), DividePrecision)).doubleValue();
            this.highLowPriceDiffAvg   = ((BigDecimal) sumHighLowPriceDiffObj.divide(new BigDecimal(timeSeries.size(), DefaultPrecision), DividePrecision)).doubleValue();
            
            if(beforeAfterDiffDayCount != 0) {
                this.adjCloseDiffAvg       = ((BigDecimal) sumAdjCloseDiffObj.divide(new BigDecimal(beforeAfterDiffDayCount, DefaultPrecision), DividePrecision)).doubleValue();
                this.overnightPriceDiffAvg = ((BigDecimal) sumOvernightPriceDiffObj.divide(new BigDecimal(beforeAfterDiffDayCount, DefaultPrecision), DividePrecision)).doubleValue();
                this.volumeDiffAvg         = ((BigDecimal) sumVolumeDiffObj.divide(new BigDecimal(beforeAfterDiffDayCount, DefaultPrecision), DividePrecision)).doubleValue();
                this.dolVolumeDiffAvg      = ((BigDecimal) sumDolVolumeDiffObj.divide(new BigDecimal(beforeAfterDiffDayCount, DefaultPrecision), DividePrecision)).doubleValue();
            } else {
                this.adjCloseDiffAvg       = 0;
                this.overnightPriceDiffAvg = 0;
                this.volumeDiffAvg         = 0;
                this.dolVolumeDiffAvg      = 0;  
            }

            BigDecimal adjCloseSumSqrDiff  = new BigDecimal(0, DefaultPrecision);
            BigDecimal volumeSumSqrDiff    = new BigDecimal(0, DefaultPrecision);
            BigDecimal dolVolumeSumSqrDiff = new BigDecimal(0, DefaultPrecision);
            
            BigDecimal adjCloseDiffSumSqrDiff       = new BigDecimal(0, DefaultPrecision);
            BigDecimal closeOpenPriceDiffSumSqrDiff = new BigDecimal(0, DefaultPrecision);
            BigDecimal highLowPriceDiffSumSqrDiff   = new BigDecimal(0, DefaultPrecision);
            BigDecimal overnightPriceDiffSumSqrDiff = new BigDecimal(0, DefaultPrecision);
            BigDecimal volumeDiffSumSqrDiff         = new BigDecimal(0, DefaultPrecision);
            BigDecimal dolVolumeDiffSumSqrDiff      = new BigDecimal(0, DefaultPrecision);
            
            for(int i = 0; i < timeSeries.size(); i++) {
                TradeEvent te = timeSeries.get(i);
                
                BigDecimal b = (new BigDecimal(this.closeOpenPriceDiffAvg, DefaultPrecision)).subtract(new BigDecimal(Math.abs(te.closeOpenPriceDiff), DefaultPrecision), DefaultPrecision);
                closeOpenPriceDiffSumSqrDiff = closeOpenPriceDiffSumSqrDiff.add(b.multiply(b, DefaultPrecision), DefaultPrecision);
                
                BigDecimal c = (new BigDecimal(this.highLowPriceDiffAvg, DefaultPrecision)).subtract(new BigDecimal(Math.abs(te.highLowPriceDiff), DefaultPrecision), DefaultPrecision);
                highLowPriceDiffSumSqrDiff = highLowPriceDiffSumSqrDiff.add(c.multiply(c, DefaultPrecision), DefaultPrecision);
                
                BigDecimal adjClosePriceDiff = (new BigDecimal(this.adjCloseAvg, DefaultPrecision)).subtract(new BigDecimal(te.adjClose, DefaultPrecision), DefaultPrecision);
                adjCloseSumSqrDiff = adjCloseSumSqrDiff.add(adjClosePriceDiff.multiply(adjClosePriceDiff, DefaultPrecision), DefaultPrecision);
                
                BigDecimal volDiff = (new BigDecimal(this.volumeAvg, DefaultPrecision)).subtract(new BigDecimal(te.volume, DefaultPrecision), DefaultPrecision);
                volumeSumSqrDiff = volumeSumSqrDiff.add(volDiff.multiply(volDiff, DefaultPrecision), DefaultPrecision);
                
                BigDecimal dolVolDiff = (new BigDecimal(this.dolVolumeAvg, DefaultPrecision)).subtract(new BigDecimal(te.dolVolume, DefaultPrecision), DefaultPrecision);
                dolVolumeSumSqrDiff = dolVolumeSumSqrDiff.add(dolVolDiff.multiply(dolVolDiff, DefaultPrecision), DefaultPrecision);
                                
                if(te.dayBefore) {
                    BigDecimal a = (new BigDecimal(this.adjCloseDiffAvg, DefaultPrecision)).subtract(new BigDecimal(Math.abs(te.adjCloseDiff), DefaultPrecision), DefaultPrecision);
                    adjCloseDiffSumSqrDiff = adjCloseDiffSumSqrDiff.add(a.multiply(a, DefaultPrecision), DefaultPrecision);
                    
                    BigDecimal d = (new BigDecimal(this.overnightPriceDiffAvg, DefaultPrecision)).subtract(new BigDecimal(Math.abs(te.overnightPriceDiff), DefaultPrecision), DefaultPrecision);
                    overnightPriceDiffSumSqrDiff = overnightPriceDiffSumSqrDiff.add(d.multiply(d, DefaultPrecision), DefaultPrecision);
                    
                    BigDecimal e = (new BigDecimal(this.volumeDiffAvg, DefaultPrecision)).subtract(new BigDecimal(Math.abs(te.volumeDiff), DefaultPrecision), DefaultPrecision);
                    volumeDiffSumSqrDiff = volumeDiffSumSqrDiff.add(e.multiply(e, DefaultPrecision), DefaultPrecision);
                    
                    BigDecimal f = (new BigDecimal(this.dolVolumeDiffAvg, DefaultPrecision)).subtract(new BigDecimal(Math.abs(te.dolVolumeDiff), DefaultPrecision), DefaultPrecision);
                    dolVolumeDiffSumSqrDiff = dolVolumeDiffSumSqrDiff.add(f.multiply(f, DefaultPrecision), DefaultPrecision);
                }
            }
            
            if((timeSeries.size() > 1)) {
                this.adjCloseStdDev  = (double) Math.sqrt(adjCloseSumSqrDiff.divide(new BigDecimal((timeSeries.size()  - 1), DefaultPrecision), DividePrecision).doubleValue());
                this.volumeStdDev    = (double) Math.sqrt(volumeSumSqrDiff.divideToIntegralValue(new BigDecimal((timeSeries.size() - 1), DefaultPrecision), DividePrecision).doubleValue());
                this.dolVolumeStdDev = (double) Math.sqrt(dolVolumeSumSqrDiff.divideToIntegralValue(new BigDecimal((timeSeries.size() - 1), DefaultPrecision), DividePrecision).doubleValue());
                this.closeOpenPriceDiffStdDev = (double) Math.sqrt(closeOpenPriceDiffSumSqrDiff.divide(new BigDecimal((timeSeries.size() - 1), DefaultPrecision), DividePrecision).doubleValue());
                this.highLowPriceDiffStdDev   = (double) Math.sqrt(highLowPriceDiffSumSqrDiff.divide(new BigDecimal((timeSeries.size() - 1), DefaultPrecision), DividePrecision).doubleValue());
            } else {
                this.adjCloseStdDev  = 0;
                this.volumeStdDev    = 0;
                this.dolVolumeStdDev = 0;
                this.closeOpenPriceDiffStdDev = 0;
                this.highLowPriceDiffStdDev   = 0;
            }
            if(beforeAfterDiffDayCount > 1) {
                this.adjCloseDiffStdDev       = (double) Math.sqrt(adjCloseDiffSumSqrDiff.divide(new BigDecimal((beforeAfterDiffDayCount - 1), DefaultPrecision), DividePrecision).doubleValue());
                this.overnightPriceDiffStdDev = (double) Math.sqrt(overnightPriceDiffSumSqrDiff.divide(new BigDecimal((beforeAfterDiffDayCount - 1), DefaultPrecision), DividePrecision).doubleValue());
                volumeDiffStdDev    = (double) Math.sqrt(volumeDiffSumSqrDiff.divide(new BigDecimal((beforeAfterDiffDayCount - 1), DefaultPrecision), DividePrecision).doubleValue());
                dolVolumeDiffStdDev = (double) Math.sqrt(dolVolumeDiffSumSqrDiff.divide(new BigDecimal((beforeAfterDiffDayCount - 1), DefaultPrecision), DividePrecision).doubleValue());
            } else {
                this.adjCloseDiffStdDev       = 0;
                this.overnightPriceDiffStdDev = 0;
                volumeDiffStdDev    = 0;
                dolVolumeDiffStdDev = 0;
            }
            
          } else {
            this.symb = "_null"; this.dateStart = "_null"; this.dateEnd = "_null";
            
            this.adjCloseAvg = (float) -1; this.adjCloseStdDev = (double) -1; this.adjClose    = (float) -1; 
            this.adjCloseMin = (float) -1; this.adjCloseMedian = (float) -1;  this.adjCloseMax = (float) -1;
                  
            this.volumeAvg = (double) -1; this.volumeStdDev = (double) -1;
            this.volumeMin = (double) -1; this.volumeMedian = (double) -1; this.volumeMax = (double) -1;
            
            this.dolVolumeAvg    = (double) -1; this.dolVolumeStdDev = (double) -1;
            this.dolVolumeMin    = (double) -1; this.dolVolumeMax    = (double) -1; this.dolVolumeMedian = (double) -1;
            
            this.adjCloseDiffAvg       = (double) -1; this.adjCloseDiffStdDev       = (double) -1;
            this.closeOpenPriceDiffAvg = (double) -1; this.closeOpenPriceDiffStdDev = (double) -1;
            this.highLowPriceDiffAvg   = (double) -1; this.highLowPriceDiffStdDev   = (double) -1;
            this.overnightPriceDiffAvg = (double) -1; this.overnightPriceDiffStdDev = (double) -1;
            this.volumeDiffAvg         = (double) -1; this.volumeDiffStdDev         = (double) -1;
            this.dolVolumeDiffAvg      = (double) -1; this.dolVolumeDiffStdDev      = (double) -1;
        } 
    }
    
    
}
